#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<unistd.h>
#include<cstdlib>
#include <ctype.h>
#include<sys/types.h>
#include<sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>


using namespace std;

const int envnum = 64;
const int baseSize = 1024;
const int argvnum = 64; //默认argv有64个
//全局的命令行参数表
char* gargv[argvnum];
int gargc = 0;
// 由于未来还要更新环境变量 因此最好设置为全局
char pwd[baseSize];
char pwdenv[baseSize];
//我的系统环境变量
char* genv[envnum];
int lastcode = 0; 

//全局变量与重定向相关
//重定向类型
#define NoneRedir   0
#define InputRedir  1
#define OutputRedir 2
#define AppRedir    3

int redir = NoneRedir;
//重定向文件
char* filename = nullptr;

// "  " file.txt 过滤空格
#define TrimSpace(pos) do{\
  while(isspace(*pos)){\
   pos++;\
  }\
}while(0)

   


string GetUser()
{
   string user = getenv("USER");
   return user.empty() ? "None" : user ;
}

string GetHostName()
{
  string HostName = getenv("HOSTNAME");
  return HostName.empty() ? "None" : HostName;
}


string GetPwd()
{
  //  string pwd = getenv("PWD");
  if(nullptr == getcwd(pwd,sizeof(pwd))) return "None";
  snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
  putenv(pwdenv);
  return pwd;
}

string LastDir()
{
   string curr = GetPwd();
   if(curr == "/" || curr == "None") return curr;
   size_t pos = curr.rfind("/"); 
   if(pos == std::string::npos) return curr;
   return curr.substr(pos+1);
}

string MakeCommandLine()
{
    char commandLine[baseSize]; 
    snprintf(commandLine,baseSize,"[%s@%s %s]# ",GetUser().c_str(),GetHostName().c_str(),LastDir().c_str());
    return commandLine;  
}

void  PrintCommandLine() //1.命令行提示符
{
  printf("%s",MakeCommandLine().c_str());
 //刷新
 fflush(stdout);
}

bool GetCommandLine(char command_buffer[],int size)
{
   char* reasult = fgets(command_buffer,size,stdin);
   if(!reasult)
   {
     return false; //获取失败
   } 
   //处理'\n'
   command_buffer[strlen(command_buffer)-1] = 0;
   if(strlen(command_buffer) == 0) return false;
   return true;
}

//清空状态
void ResetCommandLine()
{

  //由于解析不断,因此为了安全最好清空表
  memset(gargv,0,sizeof(gargv));
  gargc = 0;
  //重定向
  redir = NoneRedir;  
  filename = nullptr;

}

//解析重定向
void ParaseRedir(char command_buffer[],int len)
{

  int end = len -1;  
  while(end >= 0)
  {
    if(command_buffer[end] == '<')
    {
        redir = InputRedir;
        filename = &command_buffer[end];
        filename++;
        TrimSpace(filename);
        //过滤文件前面的空格
        command_buffer[end] = 0;//一分为2设置为0就行 strtok提取空格是返回null
        break;
    }
   else if(command_buffer[end] == '>')
   {
      //看前一个是不是>
      if(command_buffer[end-1] == '>') //追加重定向
      {
        redir = AppRedir;
        command_buffer[end] = 0;
        command_buffer[end-1] = 0;
        filename = &command_buffer[end] + 1;
        TrimSpace(filename);
        break;
      }
      else  //输出重定向
      {
         redir = OutputRedir;
         command_buffer[end] = 0;
         filename = &command_buffer[end] + 1;
         TrimSpace(filename);
         break;
      }

   }
   else //没有出现重定向符号 
   {
      end--;
   }
 } 

}

//到这里解析命令 没有重定向符号
void ParaseCommand(char command_buffer[],int len)
{
  (void)len;

  const char* div = " ";
  gargv[gargc++] = strtok(command_buffer,div);//第一次分割
  //后续切割
  while((bool)(gargv[gargc++] = strtok(nullptr,div)));
  //最后等于null时还+一次
  gargc--;
}

bool ParaseCommandLine(char command_buffer[],int len)
{
  ResetCommandLine();
  ParaseRedir(command_buffer,len);
  // printf("command start : %s\n",command_buffer);
  //先确定是否有重定向符号
  // > < >>
  //   printf("redir: %d\n",redir);
  //   printf("filename: %s\n",filename);
  //   printf("command end: %s\n",command_buffer);
  //分割符
  ParaseCommand(command_buffer,len); 
}


void debug()
{
  printf("argc : %d\n",gargc);
  for(int i = 0 ; gargv[i];i++)
  {
    printf("argv[%d] : %s\n",i,gargv[i]);
  }
}

void DoRedir()
{
  
    if(redir == InputRedir) //输入重定向
    {    
      if(filename)
      {
       int fd  = open(filename,O_RDONLY); 
       if(fd < 0)
       {
          exit(2);
       }
       dup2(fd,0);
      }
      else 
      {
        exit(1);
      }
 
    }
    else if(redir == OutputRedir) //输出重定向
    {
       if(filename)
       {
         int fd = open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);
         if(fd < 0)
         {
           exit(4);
         }
         dup2(fd,1);
       }
       else 
       {
         exit(3);
       }

    }
    else if(redir == AppRedir) //追加重定向
    {
       if(filename)
       {
         int fd = open(filename,O_WRONLY | O_CREAT | O_APPEND , 0666);
         if(fd < 0)
           exit(6);
         dup2(fd,1);
       }
       else 
       {
          exit(5);
       }
         
    }
    else 
    {
       //没有重定向
    }  
}

bool ExexcuteCommandLine()  
{
  //创建子进程
  pid_t id = fork();
  if(id < 0) return false;
  else if(id == 0)
  {
    //让子进程进行重定向
    //0.先判断 && 重定向
    //看重定向类型判断是否需要进行重定向
     DoRedir();
    //执行命令
     execvpe(gargv[0],gargv,genv);
     //退出 成功程序替换就不会执行到exit
    exit(1);
  }
  //father 获取退出信息
  int status = 0;
  pid_t rid = waitpid(id,&status,0);
  if(rid > 0)
  {
    if(WIFEXITED(status))
    {
       lastcode = WEXITSTATUS(status);
    }
    else 
    {
      lastcode = 100;
    }
    return true;
  } 
  return false;
}


void AddEnv(const char* item)
{
  int index = 0;
  while(genv[index])
  {
     index++;
  }
  //注意这里不能直接item 因为如果item在栈上开辟释放后
  //就找不到环境变量,我们需要另辟空间
  genv[index] = (char*)malloc(strlen(item)+1);
  strncpy(genv[index],item,strlen(item)+1);
  genv[++index] = nullptr;
}


//shell自己执行命令，本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
{
  if(strcmp(gargv[0],"cd") == 0)
  {
     if(gargc == 2)
     {
       chdir(gargv[1]);
       lastcode = 0;
     }
     else 
     {
       lastcode = 1;
     }
     return true;
  }
  else if(strcmp(gargv[0],"export") == 0)
  {
    if(gargc == 2)
    {
        AddEnv(gargv[1]);
       lastcode = 0;
    }
    else 
    {
      lastcode = 2;
    }
    return true; 
  }
  else if(strcmp(gargv[0],"env") == 0)
  {
    for(int i = 0 ; genv[i] ; i++)
    {
      printf("%s\n",genv[i]);
    }
    lastcode  = 0;
    return true;
  }
  else if(strcmp(gargv[0],"echo") == 0)
  {
     //$?
     if(gargc == 2)
     {
       if(gargv[1][0] == '$')
       {
         if(gargv[1][1] == '?')
         {
           printf("%d\n",lastcode);
           lastcode = 0;
         }
       }
       else 
       {
          printf("%s\n",gargv[1]); 
          lastcode = 0;
       }
    }
     return true;
 }     
  else 
  {
    lastcode = 3;
  }
  return false; 
}


void InitEnv()
{
  extern char** environ;
  int index = 0;
  //构建表
  while(environ[index])
  {
    genv[index] = (char*)malloc(strlen(environ[index])+1);
    strncpy(genv[index],environ[index],strlen(environ[index])+1);
    index++;
  }
  genv[index] = nullptr;
}



int main()
{
    InitEnv();
    char command_buffer[baseSize];
    while(true)
   {
     PrintCommandLine(); //1.命令行提示符
    // printf("\n");
    // sleep(1);    
     if(!GetCommandLine(command_buffer,baseSize)) //2.获取用户命令
     {
        continue;//获取失败 重新获取
     }

    // printf("command start : %s\n",command_buffer);
     ParaseCommandLine(command_buffer,strlen(command_buffer)); //3.分析用户命令
     // debug();
    // 检测是否为内建命令
     if(CheckAndExecBuiltCommand())
     {
       continue;     
     }
     ExexcuteCommandLine(); //4.执行命令
   } 
  return 0;
}



